Static/dynamic config controls - #750
Conversation
|
The changes in this PR will be included in the next version bump.
|
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
varlock-website | d9b7245 | Commit Preview URL Branch Preview URL |
Jul 28 2026, 06:53 AM |
varlock
@varlock/astro-integration
@varlock/cloudflare-integration
@varlock/expo-integration
@varlock/nextjs-integration
@varlock/vite-integration
@varlock/1password-plugin
commit: |
200414b to
239c7f8
Compare
…blic+dynamic keys, fix Next headers resolution under webpack externals, rename __VARLOCK_EXECUTION_PHASE (varlock-set convention), defaultDynamic=inferFromSensitive, consolidate publicDynamic API naming, harden hydration + proxy perf, astro endpoint fail-closed + explicit static-output opt-in, drop unneeded per-process cache-busting
…corator filters; register new reserved env vars; docs
239c7f8 to
cbe9b52
Compare
📦 Bundle size
dist/ only; native binaries are versioned separately and not counted here. |
…ork tests for dynamic non-inlining + html error, CF blob type includes isDynamic, docs (Next client pre-hydration caveat, CF row, SSR freshness note)
…bpack runtime chunk + turbopack client components); client-bundle assertions in dynamic scenario
…uide framework table; formatting fixes
…oy; endpoint only useful for build-once-deploy-many)
…ange platforms), with when-it-matters list
…nferFromPrefix from recipe examples (explicit @public instead); fix guide defaults example
…ed the endpoint/hydration machinery
…rink integration pages to facts + pointers, clarify boot-time (not per-access) resolution
…arlockPublicDynamicEnv global (fixes auto-init TDZ by moving it to module bottom) + docker entrypoint recipe in guide
…under one section, boot-time injection steps, trim integration pages to minimal pointers
|
@pullfrog review this PR |
There was a problem hiding this comment.
ℹ️ No critical issues — one minor correctness gap noted inline.
Reviewed changes — first-class static/dynamic config controls, decoupling build-time inlining eligibility from @sensitive, across the schema layer, runtime, CLI filters, and every framework integration.
@dynamic/@staticitem decorators +@defaultDynamicroot decorator — newprocessDynamic()inconfig-item.tscomputesisDynamic(default linkageinferFromSensitive), serialized into the blob only when it diverges fromisSensitive(consumers readisDynamic ?? isSensitive).- Inlining gates flipped from
isSensitivetoisDynamic— vite, next (webpack/turbopack loader + plugin), expo babel, andgetBuildTimeReplacementsnow skip inlining based onisDynamic ?? isSensitive; secrecy behavior (leak scan, redaction, wrangler) stays onisSensitive. - Public+dynamic browser hydration —
loadPublicDynamicEnv/setPublicDynamicEnv/getPublicDynamicEnvinruntime/env.ts, gated by a bundler-injected__varlockPublicDynamicKeysallow-list; Astro auto-injects/__varlock/public-env, plus a boot-timeglobalThis.__varlockPublicDynamicEnvpickup path. - Build-time access guard —
__VARLOCK_EXECUTION_PHASE=build(vite) makes the ENV proxy throw (or warn via_VARLOCK_DYNAMIC_BUILD_ACCESS_MODE) on public+dynamic access during build/prerender; Next marks routes dynamic via aheaders()access hook. - Metadata-first scoped filter resolution — new
@dynamicfilter selector;resolveEnvValuesForFilterresolves decorator-selector filters against item metadata only, so excluded items' value resolvers never run. - Docs + framework-tests — new Static vs Dynamic guide, reference updates, and Next/Astro/SvelteKit/Vite/expo scenarios.
I traced the secret-safety story across every changed inlining/hydration site: the blob-serialization divergence check, the per-integration gates, and the browser hydration allow-list all correctly apply isDynamic ?? isSensitive (and && !isSensitive for the public-dynamic subset), so no @sensitive value can reach a client bundle through the changed paths. The @sensitive @static inlining in getBuildTimeReplacements is an intentional, leak-scanner-backstopped opt-in. The scoped-filter preEvaluate/matches/metadataDependencyKeys logic is sound. One narrow staleness gap is noted inline.
Claude Opus | 𝕏
|
@pullfrog - review again, we changed the model |
There was a problem hiding this comment.
Important
The boot-time injection recipe embeds valid JSON in an unsafe HTML script context, which can enable script injection.
Reviewed changes: The head is unchanged since the prior Pullfrog review; this pass independently re-reviewed the full PR using the updated model.
- Revalidated static and dynamic config semantics: Traced decorator resolution, serialized graph defaults, scoped filters, and integration inlining gates.
- Rechecked public dynamic delivery: Traced endpoint hydration, browser allow-listing, build-time access guards, and the boot-time HTML injection recipe.
- Rechecked framework behavior: Covered the Astro, Next.js, Vite, SvelteKit, Expo, and Cloudflare paths and their tests and documentation.
azure/gpt-5.6-sol | 𝕏
…pe <), compute isDynamic for proxy-view items
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes: the latest commit closes both prior findings without introducing a new regression.
- Hardened JSON output for HTML embedding:
varlock load --format jsonnow escapes<as\u003C, preserving valid JSON and exact parsed values while preventing</script>from terminating the documented inline script. - Corrected proxy-view dynamic metadata:
ConfigItem.resolveMetadata()now computesisDynamicfor proxy-view items while continuing to skip resolver-implied sensitivity processing. - Added focused regression coverage: CLI serialization tests cover hostile and benign payloads, and proxy tests cover a sensitive static item under a proxy resolution view.
azure/gpt-5.6-sol | 𝕏
…time docker recipe (sed) so --format json output stays unchanged
There was a problem hiding this comment.
ℹ️ No critical issues. One minor documentation correction is noted inline.
Reviewed changes: the latest commit narrows HTML script escaping to the context that requires it while preserving the normal JSON CLI contract.
- Restored standard JSON output: reverted global
<escaping fromvarlock load --format json. - Localized script-context escaping: updated the boot-time injection recipe to transform
<into\u003Cbefore embedding the payload in HTML.
azure/gpt-5.6-sol | 𝕏
| set -e | ||
|
|
||
| # resolve only the public+dynamic subset from the container env, then escape `<` | ||
| # as `<` so a value containing `</script>` cannot break out of the tag |
There was a problem hiding this comment.
This comment says < is escaped as <, but the command actually emits \u003C and the prose below relies on that distinction. Please name \u003C here so the copied recipe explains its safety step accurately.



Adds first-class control over whether config items are static (eligible for build-time inlining) or dynamic (must stay runtime-resolved), decoupled from sensitivity. Previously the only way to keep a value out of build-time bundles was to mark it
@sensitive.Schema
@dynamic/@staticitem decorators (boolean-valued, mutually exclusive)@defaultDynamicroot decorator:true,false, orinferFromSensitiveinferFromSensitive: dynamic follows sensitivity, so existing schemas behave exactly as before. The serialized blob only carriesisDynamicwhen it diverges fromisSensitive; consumers readisDynamic ?? isSensitive.Runtime + integrations
All bundler inlining (vite, next webpack/turbopack, expo babel) now gates on
isDynamicinstead ofisSensitive. Secrecy behavior (redaction, leak scanning, sourcemap scrubbing, wrangler secrets) stays onisSensitive. Expo warns at build time when any non-inlined (sensitive or dynamic) key is referenced from native code.Public+dynamic values load in the browser via
loadPublicDynamicEnv()hydrating the sameENV.KEYproxy, served from a JSON endpoint (getPublicDynamicEnv()). Astro auto-injects/__varlock/public-envwhen public+dynamic keys exist (configurable viapublicDynamicEndpoint); Next/SvelteKit add a one-liner route. Hydration is filtered to declared public+dynamic keys; every integration (including Next, via the webpack client runtime chunk / turbopack client components) injects the declared key list (names only) into client bundles.Next.js: accessing a public+dynamic key during server rendering marks the route dynamic (runtime access hook calling
headers()), including nested component access. Works on both bundlers; a source-rewrite approach was dropped after breaking pages-router builds.Vite-based builds set
__VARLOCK_EXECUTION_PHASE=build; accessing a public+dynamic key during build/prerender errors (downgradable via_VARLOCK_DYNAMIC_BUILD_ACCESS_MODE=warn). Sensitive values are deliberately not guarded: reading them server-side during SSG is legitimate, and leak scanning polices the output.Boot-time injection alternative to the endpoint: the browser runtime auto-hydrates from a
globalThis.__varlockPublicDynamicEnvglobal if present, so a server/container entrypoint can extract the public+dynamic subset (varlock load --filter '@dynamic,!@sensitive' --format json) and inject it as a script tag into served HTML (recipe in the guide).Filtering
@dynamicfilter selector (!@dynamicfor static items) in--filterand codegenfilter=.@dynamic/@sensitive/@required) now scope resolution and validation via metadata-first resolution: item decorator metadata resolves first (no value resolvers run), the filter matches exactly, and only selected items plus deps get values resolved. Sovarlock load --filter='!@dynamic'at build time skips runtime-only vars entirely, including their@requiredchecks. A broken value that the filter evaluation itself depended on still fails the load.Validation